package client

import (
	clist "acs/concurrent_list"
	"container/list"
	"fmt"
	"runtime/debug"
	"sync"
	"sync/atomic"
	"time"
)

type ClientList struct {
	clients clist.ConcurrentList
}

func NewClientList(size int) *ClientList {
	return &ClientList{
		clients: clist.New(size),
	}
}

func (cl *ClientList) AddClient(key string, client *Client) (*list.Element, error) {
	ele := cl.clients.Append(key, client)
	client.HandleWrite()
	return ele, nil
}

func (cl *ClientList) RemoveClient(key string, ele *list.Element) error {
	cl.clients.Remove(key, ele)
	return nil
}

// GetClientByUID get the client objects with specified uid.
func (cl *ClientList) GetClientByUID(uid int) (clientItems []*Client) {
	var cCounter int32
	clientItems = make([]*Client, 0)
	ch := make(chan *Client, 1000)
	filter := func(c *Client, uid interface{}) {
		atomic.AddInt32(&cCounter, 1)
		defer atomic.AddInt32(&cCounter, -1)
		uidStr := fmt.Sprintf("%s", uid)
		if c.registerInfo.UID == &uidStr {
			ch <- c
		}
	}
	cl.MapFunc(filter, uid)
	time.Sleep(time.Microsecond * 20)
COL:
	for {
		select {
		case c := <-ch:
			clientItems = append(clientItems, c)
		default:
			if atomic.LoadInt32(&cCounter) == 0 {
				break COL
			}
			time.Sleep(time.Microsecond * 10)
		}
	}
	return clientItems
}

func (cl *ClientList) Close() error {
	for ele := range cl.clients.Iter() {
		client := ele.(*Client)
		client.Close()
	}
	return nil
}

// MapFunc 并发(根据shard的数量)地对client进行回调处理。
// 注意: client的状态能在迭代过程中产生变化.
func (cl *ClientList) MapFunc(fn func(c *Client, args interface{}), args interface{}) {
	wg := sync.WaitGroup{}
	for _, shard := range cl.clients {
		wg.Add(1)
		go func(s *clist.ConcurrentListShard) {
			defer func() {
				err := recover()
				if err != nil {
					logger.Errorf("fatal error in MapFunc:%v, %s", err, debug.Stack())
				}
				wg.Done()
			}()
			items := s.GetItems()
			s.RLock()
			var c = items.Front()
			s.RUnlock()
			if c == nil {
				return
			}
			fn(c.Value.(*Client), args)
			for ;;{
				s.RLock()
				c = c.Next()
				s.RUnlock()
				if c == nil {
					break
				}
				fn(c.Value.(*Client), args)
			}
		}(shard)
	}
	wg.Wait()
}

